import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

/**
  *这些是Redux保留的私有操作类型。
  *对于任何未知操作，您必须返回当前状态。
  *如果当前状态未定义，则必须返回初始状态。
  *请勿在代码中直接引用这些操作类型。
 */
export const ActionTypes = {
  INIT: '@@redux/INIT'
}

/**
 *创建一个包含状态树的Redux存储。
 *更改商店中数据的唯一方法是在其上调用`dispatch（）`。
 *
 *您的应用中应该只有一个商店。指定有多么不同
 *状态树的某些部分响应动作，您可以组合多个减速器
 *使用`combineReducers`进入单个reducer函数。
 *
 * reducer (Function): 接收两个参数，分别是当前的 state 树和要处理的 action，返回新的 state 树。
 *
 * [preloadedState] (any): 初始时的 state。 在同构应用中，你可以决定是否把服务端传来的 state 水合（hydrate）后传给它，
 * 或者从之前保存的用户会话中恢复一个传给它。如果你使用 combineReducers 创建 reducer，它必须是一个普通对象，
 * 与传入的 keys 保持同样的结构。否则，你可以自由传入任何 reducer 可理解的内容。
 * 
 * enhancer (Function): Store enhancer 是一个组合 store creator 的高阶函数，返回一个新的强化过的 store creator。
 * 这与 middleware 相似，它也允许你通过复合函数改变 store 接口。
 *
 * @returns {Store}一个Redux商店，可让您阅读状态，调度操作
 *并订阅更改。
 */
export default function createStore(reducer, preloadedState, enhancer) {
  // 如果初始化传入的是函数并且没有传入中间件，则初始化函数设为中间件
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    /**
     * 从这里可以看出，enhancer 的一般形式是：
     * const enhancer = (createStore) => {
     *  //返回一个函数 `finalCreateStore`，用于接收 reducer 和 preloadedState
     *  return function finalCreateStore (reducer, preloadedState) {
     *    //这里可以拿到原 createStore、reducer 和 preloadedState
     *    //然后添加自定义逻辑
     *    //最终返回 store 对象
     *    return createStore(reducer, preloadedState);
     *  }
     * }
     */
    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  //当前订阅队列，用以存放通过 subscribe 函数执行的订阅。
  let currentListeners = []
  // 这是一个很重要的设计，为的就是每次在遍历监听器的时候保证 currentListeners 数组不变
  // 可以考虑下只存在 currentListeners 的情况，如果我在某个 subscribe 中再次执行 subscribe
  // 或者 unsubscribe，这样会导致当前的 currentListeners 数组大小发生改变，从而可能导致
  // 索引出错
  let nextListeners = currentListeners
  let isDispatching = false

  //确保可以改变下一个监听者
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   *获取当前状态
   *
   * @returns {any}应用程序的当前状态树。
   */
  function getState() {
    return currentState
  }

  /**
   *添加更改侦听器。每次调度动作时都会调用它，
   *并且状态树的某些部分可能已经发生了变化。那么你可以
   *调用`getState（）`来读取回调中的当前状态树。
   *
   *您可以从更改侦听器调用`dispatch（）`，具体如下
   *警告：
   *
   * 1.订阅在每次`dispatch（）`调用之前进行快照。
   *如果您在调用侦听器时订阅或取消订阅，请执行此操作
   *不会对当前正在进行的`dispatch（）`产生任何影响。
   *但是，下一个`dispatch（）`调用，无论是否嵌套，都将使用更多
   *订阅列表的最近快照。
   *
   * 2.听众不应期望看到所有州的变化，如州
   *在嵌套的`dispatch（）`之前可能已经多次更新过
   *听众被召唤。但是，它保证所有订户
   在`dispatch（）`启动之前注册的*将使用最新的调用
   *退出时的状态。
   *
   * @param {Function} listener在每个调度上调用的回调。
   * @returns {Function}一个删除此更改侦听器的函数。
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    // 每次订阅都会维护一个标志位，防止重复取消
    let isSubscribed = true

    ensureCanMutateNextListeners()
    //添加订阅者
    nextListeners.push(listener)

    //取消订阅
    return function unsubscribe() {
      // 防止重复取消订阅时
      if (!isSubscribed) {
        return
      }

      // 取消订阅先把标志位置 设置false
      isSubscribed = false

      ensureCanMutateNextListeners()
      // 找到订阅函数在订阅队列中的位置
      const index = nextListeners.indexOf(listener)
      // 删除订阅队列中的相应订阅函数。
      nextListeners.splice(index, 1)
    }
  }

 /**
   *调度一个动作。这是触发状态变化的唯一方法。
   *
   *用于创建商店的`reducer`函数将被调用
   *当前状态树和给定的`动作`。它的返回值会
   *被认为是树的**下一个**状态，以及改变听众
   *将被通知。
   *
   *基本实现仅支持普通对象操作。如果你想
   *你需要发送一个Promise，一个Observable，一个thunk或者其他东西
   *将您的商店创建功能包装到相应的中间件中。对于
   *示例，请参阅`redux-thunk`包的文档。即便是
   *中间件最终将使用此方法调度普通对象操作。
   *
   * @param {Object} action表示“已更改的内容”的普通对象。它是
   *保持动作可序列化的好主意，以便您可以录制和重播用户
   *会话，或使用旅行`redux-devtools`的时间。必须采取行动
   *`type`属性，可能不是'undefined`。使用它是个好主意
   *动作类型的字符串常量。
   *
   * @returns {Object}为方便起见，您调度的操作对象相同。
   *
   *请注意，如果您使用自定义中间件，它可能会将`dispatch（）`换行
   *返回其他内容（例如，您可以等待的Promise）。
   */
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }

    /**
     * 注意在 Reducers 中是不能执行 dispatch 函数的
     * 因为你一旦在 reducer 函数中执行 dispatch，会引发死循环
     */
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    // 执行当前reducer,当前reducer可以被replace掉
    try {
      //分发器加锁,避免在reducer计算的过程中再进行计算。保持数据state的一致性
      isDispatching = true
      //将当前的state和action交给reducer进行计算
      currentState = currentReducer(currentState, action)
    } finally {
      //无论内部什么情况都进行释放
      isDispatching = false
    }

    // 每次发布时候，更新当前订阅函数。发布的时候再订阅，会把下次订阅的函数从当前
     // 函数拷贝一份，防止当前执行发布函数受影响
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    /**
     * 还没看懂
     */
    return action
  }

  /**
    *只做了两件事情，首先用接收的nextReducer替换内部的currentReducer，
    *接着用dispatch（{type：ActionTypes.INIT}）来初始化状态
    *替换商店当前使用的减速器以计算状态。
    *
    *如果您的应用程序实现了代码拆分，您可能需要这样做
    *动态加载一些减速器。 如果你，你可能还需要这个
    *为Redux实现热重载机制。
    *
    * @param {Function} nextReducer要使用的商店的reducer。
    * @returns {void}
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */
  /**
   * observable 是为了配合 Rxjs 这样 observable/reactive 库，不久的将来
   * EMCAScript 可能会支持原生的 observable/reactive 对象。
   */
  function observable() {
    // subscribe 就是上面的那个 subscribe订阅函数
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer) {
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          // 调用 observer 的 next 方法，获取当前 state。
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        // 将 observeState 当作一个 listener，dispatch 之后自动调用 observer 的 next 方法。
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }


  //创建商店时，将调度“INIT”操作以便每个操作
  // reducer返回其初始状态。 这有效地填补了
  //初始状态树
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch, //分发action
    subscribe, //订阅(监听)state变化
    getState, //获取当前state
    replaceReducer, //替换当前的reducer
    [$$observable]: observable
  }
}
